/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package data.email;

import com.sun.mail.imap.IMAPBodyPart;
import com.sun.mail.util.BASE64DecoderStream;
import javax.mail.Message.*;
import enums.EmailHeaderType;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import connection.Account;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import javax.mail.internet.MimeMultipart;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

/**
 * Třída reprezentuje e-mailovou zprávu.
 * @author Arcanis
 */
public class EmailMessage extends Observable {

    private Map<EmailHeaderType, EmailAddress> emailHeaders;
    private Map<EmailAddress, RecipientType> receivers;
    private String subject;
    private String body;
    private Date date;
    private List<Attachment> attachments; //@TODO
    private List<EmailAddress> cachedTo;
    private List<EmailAddress> cachedBcc;
    private List<EmailAddress> cachedCc;
    private List<EmailAddress> from;
    private Message connectedMessage = null;
    private EmailFolder folder;
    private Account account;
    private boolean readFlag = false;

    public EmailMessage(String message) {
        this();

        //@TODO Necka - rozparsovat email podle toho v jakém formátu ho dostaneš od serveru, tj ten String message si klidně uprav
    }

    public EmailMessage(Message m, Account a, EmailFolder f) throws Exception {
        this();
        this.connectedMessage = m;
        this.account = a;
        this.folder = f;
        this.date = m.getSentDate();
        LoadRecipients(m);
        LoadFrom(m);

        this.subject = m.getSubject();
        LoadMessageContent(m);
    }

    public void setRead(boolean read) {
        //@todo
        this.readFlag = read;
    }

    public boolean getReadFlag() {
        return this.readFlag;
    }

    public void LoadMessageContent(Message m) throws Exception {
        Object content = m.getContent();
        if (content instanceof Multipart) {
            Multipart multicontent = (Multipart) content;

            Part part = null;
            String disposition = null;

            for (int i = 0; i < multicontent.getCount(); i++) {
                part = multicontent.getBodyPart(i);
                disposition = part.getDisposition();

                if (Part.ATTACHMENT.equalsIgnoreCase(disposition)) {
                    this.attachments.add(new Attachment(part.getFileName(), part, this.account, this.folder));
                } else {
                    Object tst = part;
                    if (part instanceof Multipart) {
//                        System.out.println("MULTI");
                        System.out.println(part.getContentType());
                        System.out.println(part.getContent().toString());
                        part = (Part) getFromMultiPart((Multipart) tst);
                        this.body = extractBody(part.getInputStream());
                    } else if (part instanceof IMAPBodyPart) {
//                        System.out.println("IMAP");                        
                        if(part.getFileName() != null){
                            String name = part.getFileName();
                            if(name.matches("^=\\?utf-8\\?B\\?(.*)\\?=$")){
                                name = name.replaceAll("=\\?utf-8\\?B\\?", "").replaceAll("\\?=", "");
                                byte[] a = Base64.decodeBase64(name);
                                name = new String(a);
                            }
                            this.attachments.add(new Attachment(name, part, this.account, this.folder));
                        }                        
                        Object o = getFromImapPart((IMAPBodyPart) part);
                        if (o instanceof String) {
                            this.body = (String) o;
                        } else if (o instanceof BASE64DecoderStream) {
                            BASE64DecoderStream st = ((BASE64DecoderStream)o);
                            //doNothing?
//                            this.body = extractBody();
                        } else if (o instanceof Multipart) {
                            this.body = extractBody(part.getInputStream());
                        } else {                            
                            this.body = o.toString();
                        }
                    } else {
                        System.out.println("ELSE");
                        System.out.println(part.getClass());
                        this.body = extractBody(part.getInputStream());
                    }
                }
            }
        } else {
//            this.body = extractBody(m.getInputStream());
            this.body = m.getContent().toString();
        }
    }

    // @TODO conc multiparts, not just choose first
    private static Object getFromImapPart(IMAPBodyPart part) throws Exception {
        Object o = part.getContent();
        if (o instanceof Multipart) {
            return getFromMultiPart((Multipart) o);
        } else {
            return o;
        }

    }

    private static Object getFromMultiPart(Multipart multipart) throws Exception {
        Object o = null;
        for (int j = 0; j < multipart.getCount(); j++) {
            o = multipart.getBodyPart(j).getContent();
            if (o instanceof MimeMultipart) {
                return getFromMultiPart((MimeMultipart) o);
            } else if (o instanceof IMAPBodyPart) {
                return getFromImapPart((IMAPBodyPart) o);
            }
        }
        return o;
    }

    private String extractBody(InputStream is) throws Exception {
        // message body
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }       
        return sb.toString();
    }

    public void LoadFrom(Message m) throws Exception {
        String addr = null;
        Address[] addrs = null;
        addrs = m.getFrom();
        if (addrs != null) {
            for (Address a : addrs) {
                if (a instanceof InternetAddress) {
                    addr = ((InternetAddress) a).getAddress();
                    from.add(new EmailAddress(addr));
                }
            }
        }
    }

    public void LoadRecipients(Message m) throws Exception {
        System.out.println();
        String addr = null;
        Address[] addrs = null;
        addrs = m.getRecipients(RecipientType.TO);
        if (addrs != null) {
            for (Address a : addrs) {
                if (a instanceof InternetAddress) {
                    addr = ((InternetAddress) a).getAddress();
//                    System.out.println("to "+addr);
                    receivers.put(new EmailAddress(addr), RecipientType.TO);
                }
            }
        }

        addrs = m.getRecipients(RecipientType.CC);
        if (addrs != null) {
            for (Address a : addrs) {
                if (a instanceof InternetAddress) {
                    addr = ((InternetAddress) a).getAddress();
//                    System.out.println("cc "+addr);
                    receivers.put(new EmailAddress(addr), RecipientType.CC);
                }
            }
        }
        addrs = m.getRecipients(RecipientType.BCC);
        if (addrs != null) {
            for (Address a : addrs) {
                if (a instanceof InternetAddress) {
                    addr = ((InternetAddress) a).getAddress();
//                    System.out.println("bcc "+addr);
                    receivers.put(new EmailAddress(addr), RecipientType.BCC);
                }
            }
        }
    }

    public EmailMessage() {
        //Vytvoří prázdný email, abychom ho mohli naplnit ručně
        this.attachments = new LinkedList<>();
        receivers = new Hashtable<>();
        cachedBcc = new ArrayList<>();
        cachedCc = new ArrayList<>();
        cachedTo = new ArrayList<>();
        this.from = new ArrayList<>();
    }

    public Attachment[] getAllAttachments() {
        Attachment[] atts = new Attachment[this.attachments.size()];
        return this.attachments.toArray(atts);
    }

    public void addAttachment(Attachment a) {
        attachments.add(a);
    }

    public void removeAttachmentAt(int i) {
        attachments.remove(i);
    }

    public Attachment getAttachmentAt(int i) {
        return attachments.get(i);
    }

    public EmailAddress[] getFrom() {
        EmailAddress[] addrs = new EmailAddress[this.from.size()];
        return this.from.toArray(addrs);
    }

    public void addFrom(String email) {
        if (!this.from.contains(email)) {
            this.from.add(new EmailAddress(email));
        }
    }

    public void removeFrom(String email) {
        this.from.remove(new EmailAddress(email));
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
        setChanged();
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
        setChanged();
    }

    public Iterator<Map.Entry<EmailAddress, RecipientType>> getIterator() {
        return this.receivers.entrySet().iterator();
    }

    public Set<Map.Entry<EmailAddress, RecipientType>> getEntrySet() {
        return this.receivers.entrySet();
    }

    public Map<EmailHeaderType, EmailAddress> getEmailHeaders() {
        return emailHeaders;
    }

    public void setEmailHeaders(Map<EmailHeaderType, EmailAddress> emailHeaders) {
        this.emailHeaders = emailHeaders;
        setChanged();
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
        setChanged();
    }

    public void clearAllReceivers() {
        this.receivers.clear();
        this.cachedTo.clear();
        this.cachedCc.clear();
        this.cachedBcc.clear();
    }

    public void addTo(EmailAddress to) {
        addReceiver(to, RecipientType.TO);
    }

    public void addCC(EmailAddress cc) {
        addReceiver(cc, RecipientType.CC);
    }

    public void addBcc(EmailAddress bcc) {
        addReceiver(bcc, RecipientType.BCC);
    }

    public void removeRecipient(EmailAddress addr) {
        RecipientType type = null;
        if (this.receivers.containsKey(addr)) {
            type = this.receivers.get(addr);
            this.receivers.remove(addr);

            // Handle caches
            if (type.equals(RecipientType.TO)) {
                this.cachedTo.remove(addr);
            } else if (type.equals(RecipientType.CC)) {
                this.cachedCc.remove(addr);
            } else if (type.equals(RecipientType.BCC)) {
                this.cachedBcc.remove(addr);
            }
        }
    }

    private void addReceiver(EmailAddress to, RecipientType type) {
        List<EmailAddress> newList = null;

        if (type.equals(RecipientType.TO)) {
            newList = cachedTo;
        } else if (type.equals(RecipientType.CC)) {
            newList = cachedCc;
        } else if (type.equals(RecipientType.BCC)) {
            newList = cachedBcc;
        }

        RecipientType storedType = null;
        storedType = this.receivers.get(to);

        // Adding to this list, 
        if (storedType == null) {
            if (newList != null && !newList.isEmpty()) {
                newList.clear();
            }
        } else if (storedType != type) {
            if (newList != null && !newList.isEmpty()) {
                newList.clear();
            }

            List<EmailAddress> formerList = null;
            if (type.equals(RecipientType.TO)) {
                formerList = cachedTo;
            } else if (type.equals(RecipientType.CC)) {
                formerList = cachedCc;
            } else if (type.equals(RecipientType.BCC)) {
                formerList = cachedBcc;
            }

            if (formerList != null && !formerList.isEmpty()) {
                formerList.clear();
            }
        }

        this.receivers.put(to, type);
    }

    public List<EmailAddress> getTo() {
        if (this.cachedTo.isEmpty()) {
            refreshCache(RecipientType.TO);
        }

        return this.cachedTo;
    }

    public List<EmailAddress> getCc() {
        if (this.cachedCc.isEmpty()) {
            refreshCache(RecipientType.CC);
        }

        return this.cachedCc;
    }

    public List<EmailAddress> getBcc() {
        if (this.cachedBcc.isEmpty()) {
            refreshCache(RecipientType.BCC);
        }

        return this.cachedBcc;
    }

    private void refreshCache(RecipientType type) {
//        System.out.println("Refreshing cache "+type.toString());
        List<EmailAddress> tmpList = null;
        if (type.equals(RecipientType.TO)) {
//            System.out.println(RecipientType.TO);
            tmpList = cachedTo;
        } else if (type.equals(RecipientType.CC)) {
//            System.out.println(RecipientType.CC);
            tmpList = cachedCc;
        } else if (type.equals(RecipientType.BCC)) {
//            System.out.println(RecipientType.BCC);
            tmpList = cachedBcc;
        }

        if (tmpList != null) {
            tmpList.clear();
            Iterator<EmailAddress> it = this.receivers.keySet().iterator();
            while (it.hasNext()) {
                EmailAddress address = it.next();
                RecipientType currentType = this.receivers.get(address);
                if (currentType.equals(type)) {
//                    System.out.println(address.getAddress());
                    tmpList.add(address);
                }
            }
        }
//        System.out.println("Cache end");
        // @TODO add getrecipients
    }
}
